#include "BadSqlQueryException.hpp"

#define A(str) encode<EncodingT,ansi>(str)
#define C(str) encode<ansi,EncodingT>(str)

NAMESPACE_BEGIN(data_access)

template <class EncodingT>
sqlite3 * _DataConnection<EncodingT>::connection_handle = NULL;

template <class EncodingT>
_DataConnection<EncodingT> * _DataConnection<EncodingT>::m_instance = NULL;

// constructor
template <class EncodingT>
_DataConnection<EncodingT>::_DataConnection()
{
    m_logger = &Category::getInstance(LOGNAME);
    m_logger->debugStream() << "creation DataConnection";
    transaction_inProgress = false;
    //initialization code
}

template <class EncodingT>
bool _DataConnection<EncodingT>::openConnection(typename EncodingT::string_t const& host,
                                                int port,
                                                typename EncodingT::string_t const& db,
                                                typename EncodingT::string_t const& user,
                                                typename EncodingT::string_t const& passwd)
{
    //open connection
    //initialization connection
    if (sqlite3_open(A(db).c_str(), &connection_handle) != SQLITE_OK) {
        return false;
    }
    return true;
}

template <class EncodingT>
void _DataConnection<EncodingT>::closeConnection()
{
    //close connection
    sqlite3_close(connection_handle);
    //m_logger->debugStream() << "close connection";
}

template <class EncodingT>
_DataConnection<EncodingT> * _DataConnection<EncodingT>::getInstance()
{
    //check connection and create DataConnection
    if (connection_handle) {
        if (!m_instance)
            m_instance = new _DataConnection<EncodingT>();
        return m_instance;
    }
    else return NULL;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatSelect(vector<typename EncodingT::string_t> const& columns,
                                                    vector<typename EncodingT::string_t> const& tables,
                                                    typename EncodingT::string_t const& filter, bool forUpdate, bool noWait) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("SELECT ");
    if (columns.size() == 0) {
        m_logger->fatalStream() << "bad query : no columns defined in SELECT query";
        throw BadSqlQueryException("no columns defined in SELECT query");
    }
    else query += columns[0];
    for (size_t i=1; i<columns.size(); i++) query += C(", ") + columns[i];
    query += C(" FROM ");
    if (tables.size() == 0) {
        m_logger->fatalStream() << "bad query : no tables defined in SELECT query";
        throw BadSqlQueryException("no tables defined in SELECT query");
    }
    else query += tables[0];
    for (size_t i=1; i<tables.size(); i++) query += C(", ") + tables[i];
    if (filter != EncodingT::EMPTY) {
        query += C(" WHERE ") + filter;
    }
    if (forUpdate) query += C(" FOR UPDATE ");
    if (noWait) query += C(" NOWAIT ");
    query += C(";");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
typename _DataConnection<EncodingT>::resultset
_DataConnection<EncodingT>::select(vector<typename EncodingT::string_t> const& columns,
                                                    vector<typename EncodingT::string_t> const& tables,
                                                    typename EncodingT::string_t const& filter) const
{
    sqlite3_stmt *statement;
    int res;
    resultset result;
    typename EncodingT::string_t query = formatSelect(columns, tables, filter, false, false);
    if (sqlite3_prepare_v2(connection_handle,A(query).c_str(),-1,&statement,NULL) != SQLITE_OK){
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    size_t num_fields = sqlite3_column_count(statement); 
    while ((res=sqlite3_step(statement)) == SQLITE_ROW) {
        vector<typename EncodingT::string_t> one_row;
        for (size_t i=0; i<num_fields; ++i) {
            const char * str = (const char*)sqlite3_column_text(statement,i);
            one_row.push_back(C(str?str:""));
        }
        result.push_back(one_row);
    }
    sqlite3_finalize(statement);
    return result;
}

template <class EncodingT>
typename _DataConnection<EncodingT>::resultset
_DataConnection<EncodingT>::selectForUpdate(vector<typename EncodingT::string_t> const& columns,
                                                    vector<typename EncodingT::string_t> const& tables,
                                                    typename EncodingT::string_t const& filter, bool nowait)
{
    sqlite3_stmt *statement;
    int res;
    resultset result;
    typename EncodingT::string_t query = formatSelect(columns, tables, filter, false, nowait);
    if (sqlite3_prepare_v2(connection_handle,A(query).c_str(),-1,&statement,NULL) != SQLITE_OK){
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    size_t num_fields = sqlite3_column_count(statement); 
    while ((res=sqlite3_step(statement)) == SQLITE_ROW) {
        vector<typename EncodingT::string_t> one_row;
        for (size_t i=0; i<num_fields; ++i) {
            const char * str = (const char*)sqlite3_column_text(statement,i);
            one_row.push_back(C(str?str:""));
        }
        result.push_back(one_row);
    }
    sqlite3_finalize(statement);
    return result;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatInsert(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("INSERT INTO ");
    if (table == EncodingT::EMPTY) {
        m_logger->fatalStream() << "bad query : no table defined in INSERT query";
        throw BadSqlQueryException("no table defined in INSERT query");
    }
    else query += table;
    query += C(" ( ");
    if (columns.size() == 0) {
        m_logger->fatalStream() << "bad query : no columns defined in INSERT query";
        throw BadSqlQueryException("no columns defined in INSERT query");
    }
    else query += columns[0];
    for (size_t i=1; i<columns.size(); i++) query += C(", ") + columns[i];
    query += C(" ) VALUES ( ");
    if (values.size() == 0) {
        m_logger->fatalStream() << "bad query : no values defined in INSERT query";
        throw BadSqlQueryException("no values defined in INSERT query");
    }
    else query += values[0];
    for (size_t i=1; i<values.size(); i++) query += C(", ") + values[i];
    query += C(" );");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::insert(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values)
{
    int res;
    sqlite3_stmt *statement;
    typename EncodingT::string_t query = formatInsert(table, columns, values);
    if (sqlite3_prepare_v2(connection_handle,A(query).c_str(),-1,&statement,NULL) != SQLITE_OK){
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    if ((res=sqlite3_step(statement)) != SQLITE_DONE) {
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    sqlite3_finalize(statement);
    return true;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatDeleteFrom(typename EncodingT::string_t const& table,
                                        typename EncodingT::string_t const& filter) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("DELETE FROM ");
    if (table == EncodingT::EMPTY) {
        m_logger->fatalStream() << "bad query : no table defined in DELETE query";
        throw BadSqlQueryException("no table defined in DELETE query");
    }
    else query += table;
    if (filter != EncodingT::EMPTY) {
        query += C(" WHERE ") + filter;
    }
    query += C(";");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::deleteFrom(typename EncodingT::string_t const& table,
                                        typename EncodingT::string_t const& filter)
{
    int res;
    sqlite3_stmt *statement;
    typename EncodingT::string_t query = formatDeleteFrom(table, filter);
    if (sqlite3_prepare_v2(connection_handle,A(query).c_str(),-1,&statement,NULL) != SQLITE_OK){
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    if ((res=sqlite3_step(statement)) != SQLITE_DONE) {
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    sqlite3_finalize(statement);
    return true;
}

template <class EncodingT>
typename EncodingT::string_t _DataConnection<EncodingT>::formatUpdate(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values,
                                        typename EncodingT::string_t const& filter) const
{
    //Create SQL query
    typename EncodingT::string_t query = C("UPDATE ");
    if (table == EncodingT::EMPTY) {
        m_logger->fatalStream() << "bad query : no table defined in UPDATE query";
        throw BadSqlQueryException("no table defined in UPDATE query");
    }
    else query += table;
    query += C(" SET ");
    if (columns.size() == 0 || values.size() == 0) {
        m_logger->fatalStream() << "bad query : no columns defined in UPDATE query";
        throw BadSqlQueryException("no columns defined in UPDATE query");
    }
    if (columns.size() != values.size()) {
        m_logger->fatalStream() << "bad query : invalid columns and values sizes in UPDATE query";
        throw BadSqlQueryException("invalid columns and values sizes in UPDATE query");
    }
    else query += columns[0] + C("=") + values[0];
    for (size_t i=1; i<columns.size(); i++) query += C(", ") + columns[i] + C("=") + values[i];
    if (filter != EncodingT::EMPTY) {
        query += C(" WHERE ") + filter;
    }
    query += C(";");
    m_logger->debugStream() << "query : " << A(query);
    return query;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::update(typename EncodingT::string_t const& table,
                                        vector<typename EncodingT::string_t> const& columns,
                                        vector<typename EncodingT::string_t> const& values,
                                        typename EncodingT::string_t const& filter)
{
    int res;
    sqlite3_stmt *statement;
    typename EncodingT::string_t query = formatUpdate(table, columns, values, filter);
    if (sqlite3_prepare_v2(connection_handle,A(query).c_str(),-1,&statement,NULL) != SQLITE_OK){
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    if ((res=sqlite3_step(statement)) != SQLITE_DONE) {
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    sqlite3_finalize(statement);
    return true;
}

template <class EncodingT>
int _DataConnection<EncodingT>::getLastInsertID() const
{
    return (int) sqlite3_last_insert_rowid(connection_handle);
}

template <class EncodingT>
int _DataConnection<EncodingT>::selectMaxID(typename EncodingT::string_t const& id,
                                            typename EncodingT::string_t const& table) const
{
    int result = 0;
    sqlite3_stmt *statement;
    int res;
    string query = string("SELECT max(") + A(id) + ") FROM "+ A(table) + ";";
    m_logger->debugStream() << "query : " << query;
    if (sqlite3_prepare_v2(connection_handle,query.c_str(),-1,&statement,NULL) != SQLITE_OK){
        m_logger->fatalStream() << "bad query : " << sqlite3_errmsg(connection_handle);
        throw BadSqlQueryException(sqlite3_errmsg(connection_handle));
    }
    size_t num_fields = sqlite3_column_count(statement); 
    while ((res=sqlite3_step(statement)) == SQLITE_ROW) {
        if (num_fields) result = sqlite3_column_int(statement,0);
    }
    sqlite3_finalize(statement);    
    return result;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::commit()
{
    static char * commit = "COMMIT;";
    string query = commit;
    m_logger->debugStream() << "query : " << query;
    sqlite3_exec(connection_handle,query.c_str(),NULL,NULL,NULL);
    transaction_inProgress = false;
    return true;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::rollback()
{
    static char * rollback = "ROLLBACK;";
    string query = rollback;
    m_logger->debugStream() << "query : " << query;
    sqlite3_exec(connection_handle,query.c_str(),NULL,NULL,NULL);
    transaction_inProgress = false;
    return true;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::startTransaction()
{
    static char * start_transaction = "BEGIN TRANSACTION;";
    string query = start_transaction;
    m_logger->debugStream() << "query : " << query;
    sqlite3_exec(connection_handle,query.c_str(),NULL,NULL,NULL);
    transaction_inProgress = true;
    return true;
}

template <class EncodingT>
bool _DataConnection<EncodingT>::isTransactionInProgress()
{
    return transaction_inProgress;
}

NAMESPACE_END

#undef C
#undef A

